package com.ruoyi.framework.config;

import com.ruoyi.framework.config.properties.KeycloakConfig;
import com.ruoyi.framework.config.properties.PermitAllUrlProperties;
import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
import com.ruoyi.framework.security.handle.OAuth2LoginSuccessHandler;
import com.ruoyi.framework.security.handle.OAuth2LogoutHandler;
import com.ruoyi.framework.security.handle.OAuth2LogoutSuccessHandler;
import com.ruoyi.framework.web.service.TokenService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.endpoint.*;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.filter.CorsFilter;

import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;

@Configuration
@EnableWebSecurity
@Slf4j
public class OAuth2SecurityConfig {

    @Resource
    private OAuth2LoginSuccessHandler customLoginSuccessHandler;

    @Resource
    private KeycloakConfig keycloakConfig;

    @Resource
    private OAuth2LogoutHandler customLogoutHandler;

    @Resource
    private ClientRegistrationRepository clientRegistrationRepository;

    @Resource
    RestTemplate restTemplate;

    @Resource
    RestTemplateConfig restTemplateConfig;

    @Resource
    private CorsFilter corsFilter;

    @Resource
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    @Resource
    private TokenService tokenService;

    @Resource
    @Lazy
    private PermitAllUrlProperties permitAllUrl;

    @Resource
    private AuthenticationEntryPointImpl unauthorizedHandler;

    // 保存keycloak的 sid 与 应用中关联的 session id，为反向登出时使用
    public static final ConcurrentHashMap<String, String> sessionMap = new ConcurrentHashMap<>();
    // 保存keycloak的 sid 与 应用原本的登录 token，为反向登出时使用
    public static final ConcurrentHashMap<String, String> tokenMap = new ConcurrentHashMap<>();

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .requiresChannel(channel -> channel
                        .anyRequest().requiresSecure()
                )
                // 认证失败处理类
                .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
                .authorizeHttpRequests(authorize -> {
                    permitAllUrl.getUrls().forEach(url -> authorize.requestMatchers(url).permitAll());
                    authorize
                            .requestMatchers("/sso/oauth2/backChannelLogout", "/error", "/logout").permitAll()
                            .requestMatchers("/login", "/register", "/captchaImage").permitAll()
                            .requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll()
                            .requestMatchers("/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**", "/druid/**").permitAll()
                            .anyRequest().authenticated();
                    }
                )
                // 添加JWT filter
                .addFilterAfter(authenticationTokenFilter, OAuth2LoginAuthenticationFilter.class)
                .addFilterBefore(corsFilter, OAuth2LoginAuthenticationFilter.class)
                .addFilterBefore(corsFilter, LogoutFilter.class)

                // OIDC/OAuth2 登录相关
                .oauth2Login(oauth2Login -> oauth2Login
                        .userInfoEndpoint(userInfo -> userInfo.userService(oauth2UserService()))
                        .tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenResponseClient(accessTokenResponseClient()))
                        .successHandler(customLoginSuccessHandler))
                .logout(logout -> logout
                        .logoutUrl("/logout")
                        .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "POST"))
                        .addLogoutHandler(customLogoutHandler)
                        .logoutSuccessHandler(oidcLogoutSuccessHandler())
                        .invalidateHttpSession(true)
                        .deleteCookies("JSESSIONID")
                        .clearAuthentication(true)
                )
                .sessionManagement(sessionManagement -> sessionManagement
                        .maximumSessions(-1)
                        .sessionRegistry(sessionRegistry())
                );

        return http.build();
    }

    private LogoutSuccessHandler oidcLogoutSuccessHandler() {
        OAuth2LogoutSuccessHandler successHandler =
                new OAuth2LogoutSuccessHandler(this.clientRegistrationRepository, this.keycloakConfig, this.tokenService);
        successHandler.setPostLogoutRedirectUri(keycloakConfig.getLogoutSuccessUrl());
        return successHandler;
    }

    // 使用自定义的 RestTemplate 配置，里面添加了自定义信任证书库
    @Bean
    JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri(keycloakConfig.getJwkSetUri())
                .restOperations(restTemplate)
                .build();
    }

    // 使用自定义的 RestTemplate 配置，里面添加了自定义信任证书库
    private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
        RestTemplate restTemplate = restTemplateConfig.createRestTemplate();
        restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());

        DefaultOAuth2UserService userService = new DefaultOAuth2UserService();
        userService.setRestOperations(restTemplate);
        return userService;
    }

    // 使用自定义的 RestTemplate 配置，里面添加了自定义信任证书库
    private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient(){
        RestTemplate restTemplate = restTemplateConfig.createRestTemplate();
        restTemplate.setMessageConverters(Arrays.asList(
                new FormHttpMessageConverter(),
                new OAuth2AccessTokenResponseHttpMessageConverter()));
        restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());

        DefaultAuthorizationCodeTokenResponseClient tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
        tokenResponseClient.setRestOperations(restTemplate);
        return tokenResponseClient;
    }

    // 使用自定义的 RestTemplate 配置，里面添加了自定义信任证书库
    @Bean
    public OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenResponseClient() {
        RestTemplate restTemplate = restTemplateConfig.createRestTemplate();
        restTemplate.setMessageConverters(Arrays.asList(
                new FormHttpMessageConverter(),
                new OAuth2AccessTokenResponseHttpMessageConverter()));
        restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
        restTemplate.getInterceptors().add((request, body, execution) -> {
            return execution.execute(request, body);
        });

        DefaultRefreshTokenTokenResponseClient client = new DefaultRefreshTokenTokenResponseClient();
        client.setRestOperations(restTemplate);
        return client;
    }

    // 使用自定义的 RestTemplate 配置（替换了 refreshTokenResponseClient），里面添加了自定义信任证书库
    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrations,
            OAuth2AuthorizedClientRepository authorizedClients,
            OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenResponseClient) {
        OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                .authorizationCode()
                .refreshToken(refreshConfigurer -> refreshConfigurer
                        .accessTokenResponseClient(refreshTokenResponseClient))
                .build();
        DefaultOAuth2AuthorizedClientManager manager = new DefaultOAuth2AuthorizedClientManager(
                clientRegistrations, authorizedClients);
        manager.setAuthorizedClientProvider(authorizedClientProvider);

        return manager;
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    @Bean
    public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
        return new ServletListenerRegistrationBean<>(new HttpSessionEventPublisher());
    }

}